# main.py

import hashlib
import struct

offset64 = 14695981039346656037
prime64 = 1099511628211


# hashNew initializes a new fnv64a hash value.
def hash_new() -> int:
    return offset64


# hashAdd adds a string to a fnv64a hash value, returning the updated hash.
def hash_add(h: int, s: str) -> int:
    for b in s.encode("utf-8"):
        h ^= b
        h *= prime64
    return h


# hashAddByte adds a byte to a fnv64a hash value, returning the updated hash.
def hash_add_byte(h: int, b: int) -> int:
    h ^= b
    h *= prime64
    return h


# A LabelName is a key for a LabelSet or Metric. It has a value associated
# therewith.
LabelName = str

# A LabelValue is an associated value for a LabelName.
LabelValue = str

LabelSet = dict[LabelName, LabelValue]

# Fingerprint provides a hash-capable representation of a Metric.
# For our purposes, FNV-1A 64-bit is used.
Fingerprint = int


def fingerprint_to_string(f: Fingerprint) -> str:
    return format(f, "016x")


# cache the signature of an empty label set.
empty_label_signature = hash_new()


# Fingerprint returns the LabelSet's fingerprint.
def label_set_to_fingerprint(ls: LabelSet) -> Fingerprint:
    if len(ls) == 0:
        return Fingerprint(empty_label_signature)

    label_names = sorted(ls.keys())

    sum_ = hash_new()
    for label_name in label_names:
        sum_ = hash_add(sum_, label_name)
        sum_ = hash_add_byte(sum_, 255)
        sum_ = hash_add(sum_, ls[label_name])
        sum_ = hash_add_byte(sum_, 255)
    return Fingerprint(sum_)


# A Metric is similar to a LabelSet, but the key difference is that a Metric is
# a singleton and refers to one and only one stream of samples.
Metric = LabelSet


def main():
    myset = {
        "a": "b",
        "c": "d",
    }

    metric = {LabelName(k): LabelValue(v) for k, v in myset.items()}

    fp = fingerprint_to_string(label_set_to_fingerprint(metric))
    print(fp)


if __name__ == "__main__":
    main()
